﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Drawing.Imaging;
using MLRobotic.Tower.Common;
using System.IO;

namespace MLRobotic.Tower.Detection
{
    // http://davidthomasbernal.com/blog/2008/03/13/c-image-processing-performance-unsafe-vs-safe-code-part-i/

    public class ImageProcessor
    {
        private Log _log = new Log("ImageProcessor");

        private Config _config;
        
        private int _width;
        private int _height;
        private PixelFormat _pixelFormat;
        private int _bytesPerPixel;

        private byte[] _buffer;
        private byte[] _mask;

        private Clustering _clustering;

        // Image returned to caller... beware of concurrent access ;)
        private Bitmap _bufferImage;
        private Bitmap _maskImage; 

        private int _imageCounter = 1;
        private int _dropCounter = 0;

        private List<bool[]> _candlesHistory = new List<bool[]>();

        public Bitmap LastResult { get { return _bufferImage; } }

        public ImageProcessor(Config config)
        {
            _config = config;

            _width = config.Width;
            _height = config.Height;
            _pixelFormat = config.PixelFormat;

            _bytesPerPixel = BitmapHelper.GetBytesPerPixel(_pixelFormat);

            _buffer = new byte[_height * _width * _bytesPerPixel];
            _mask = new byte[_height * _width * _bytesPerPixel];

            _bufferImage = new Bitmap(_width, _height, _pixelFormat);
            _maskImage = new Bitmap(_width, _height, _pixelFormat);

            _clustering = new Clustering(_width, _height, _bytesPerPixel, 4, 4);
        }

        #region Public methods

        public void SetImage(IntPtr frame)
        {
            //BitmapHelper.CopyAndInvertFrame(frame, _buffer, _width, _height, _bytesPerPixel);
            Marshal.Copy(frame, _buffer, 0, _buffer.Length);
        }

        public void SetImage(Bitmap bitmap)
        {
            BitmapHelper.BitmapToBuffer(bitmap, _buffer, _pixelFormat);
        }

        public Color CalculateMeanColor(Rectangle target)
        {
            return BitmapHelper.CalculateMeanColor(_buffer, target, _width, _height, _bytesPerPixel);
        }

        public Dictionary<TargetColor, IEnumerable<Rectangle>> Find(IEnumerable<TargetColor> targetColors, Rectangle analyzeArea, bool displayMask)
        {
            _log.Debug("Find");

            MatchTargetColor(targetColors, analyzeArea, _buffer, _mask, _width, _height, _bytesPerPixel, _pixelFormat);

            var result = new Dictionary<TargetColor, IEnumerable<Rectangle>>();
            foreach (var targetColor in targetColors)
            {
                var objects = _clustering.FindClusters(_mask, targetColor.Key, analyzeArea);
                result.Add(targetColor, objects);
            }

            DrawResultImage(result, _buffer, _mask, _bufferImage, _maskImage, analyzeArea, _pixelFormat, displayMask);

            return result;
        }

        public bool[] FindCandlesBySplittingPoints(bool displayMask)
        {
            _log.Debug("FindCandlesBySplittingPoints");

            var analyzeArea = _config.CandleArea;
            var targetColors = new List<TargetColor> { _config.Red, _config.Blue };

            MatchTargetColor(targetColors, analyzeArea, _buffer, _mask, _width, _height, _bytesPerPixel, _pixelFormat);

            var clusters = new Dictionary<TargetColor, IEnumerable<Rectangle>>();
            foreach (var targetColor in targetColors)
            {
                var objects = _clustering.FindClusters(_mask, targetColor.Key, analyzeArea);
                clusters.Add(targetColor, objects);
            }

            Dictionary<TargetColor, IEnumerable<Rectangle>> result;
            bool[] candles = FindCandlesFromClustersBySplittingPoints(targetColors.First(), targetColors.Last(), clusters, out result);

            DrawResultImage(result, _buffer, _mask, _bufferImage, _maskImage, analyzeArea, _pixelFormat, displayMask);

            return candles;
        }

        public bool[] FindCandlesBySplittingArea(bool displayMask)
        {
            _log.Debug("FindCandlesBySplittingArea");

            var analyzeArea = _config.CandleArea;
            var targetColors = new List<TargetColor> { _config.Blue, _config.Red };

            MatchTargetColor(targetColors, analyzeArea, _buffer, _mask, _width, _height, _bytesPerPixel, _pixelFormat);

            IList<Rectangle> candlesAreas = ExtractCandleAreas(analyzeArea, _config.Team).ToList();
            
            double[] blueCounts, redCounts;
            CountColorsInCandleAreas(targetColors, candlesAreas, out blueCounts, out redCounts);

            // Normalize count to be in [0:1] => same weight even if detection quality is different for each color
            Normalize(blueCounts);
            Normalize(redCounts);

            // Compare count results
            bool[] candles = ColorCountsToCandles(blueCounts, redCounts);

            var drawingResult = PrepareDrawingCandlesFromAreas(candlesAreas, candles);

            DrawResultImage(drawingResult, _buffer, _mask, _bufferImage, _maskImage, analyzeArea, _pixelFormat, displayMask);

            return candles;
        }

        public bool[] FindCandlesByVectors(bool displayMask)
        {
            _log.Debug("FindCandlesByVectors");

            var analyzeArea = _config.CandleArea;
            var targetColors = new List<TargetColor> { _config.Yellow, _config.Blue, _config.Red };

            MatchTargetColor(targetColors, analyzeArea, _buffer, _mask, _width, _height, _bytesPerPixel, _pixelFormat);

            var clusters = new Dictionary<TargetColor, IEnumerable<Rectangle>>();
            foreach (var targetColor in targetColors)
            {
                var objects = _clustering.FindClusters(_mask, targetColor.Key, analyzeArea);
                clusters.Add(targetColor, objects);
            }

            Dictionary<TargetColor, IEnumerable<Rectangle>> result;
            bool[] candles = FindCandlesFromClustersByVectors(_config.Yellow, _config.Blue, _config.Red, clusters, out result);

            DrawResultImage(result, _buffer, _mask, _bufferImage, _maskImage, analyzeArea, _pixelFormat, displayMask);

            return candles;
        }

        public IEnumerable<Point> FindBeaconsByVectors(bool displayMask)
        {
            _log.Debug("FindBeaconsByVectors");

            var analyzeArea = _config.BeaconArea;
            var targetColors = new List<TargetColor> { _config.Green, _config.Magenta };

            MatchTargetColor(targetColors, analyzeArea, _buffer, _mask, _width, _height, _bytesPerPixel, _pixelFormat);

            var clusters = new Dictionary<TargetColor, IEnumerable<Rectangle>>();
            foreach (var targetColor in targetColors)
            {
                var objects = _clustering.FindClusters(_mask, targetColor.Key, analyzeArea);
                clusters.Add(targetColor, objects);
            }

            Dictionary<TargetColor, IEnumerable<Rectangle>> result;
            IEnumerable<Point> beacons = FindBeaconsFromClusters(targetColors.First(), targetColors.Last(), clusters, out result);

            DrawResultImage(result, _buffer, _mask, _bufferImage, _maskImage, analyzeArea, _pixelFormat, displayMask);

            return beacons;
        }

        public bool[] AverageCandlesOnHistory(bool[] candles)
        {
            _log.DebugStartWatch("AverageCandlesOnHistory");

            int candleCount = 10;

            if (_candlesHistory.Count >= 10)
                _candlesHistory.RemoveAt(0);

            _candlesHistory.Add(candles);

            bool[] newCandles = new bool[candleCount];
            int[] counts = new int[candleCount];

            for (int i = 0; i < candleCount; i++)
            {
                foreach (var current in _candlesHistory)
                {
                    if (i < current.Count() && current[i])
                        counts[i]++;
                }
            }

            int[] toTest = new int[] { 1, 2, 3, 7, 8, 9 };
            for (int i = 0; i < 2; i++)
            {
                int jmax = 1;
                foreach (int j in toTest)
                {
                    if (counts[j] > counts[jmax])
                        jmax = j;
                }

                counts[jmax] = -1;
                newCandles[jmax] = true;
            }

            newCandles[0] = true;
            newCandles[4] = true;
            newCandles[5] = true;
            newCandles[6] = true;

            _log.DebugElapsed("AverageCandlesOnHistory");

            return newCandles;
        }

        public Point GetBeaconCoordinates(Point beaconRelativePolar)
        {
            _log.Debug("GetBeaconCoordinates");

            double aRadian = beaconRelativePolar.X * 6.28318530 / 360;
            double d = beaconRelativePolar.Y;

            // Coord from beacon
            int x = (int)(Math.Cos(aRadian) * d);
            int y = (int)(Math.Sin(aRadian) * d);

            if (x < 0) x = 0;
            if (y < 0) y = 0;
            if (x > 3000) x = 3000;
            if (y > 2000) x = 2000;

            // Coord from Blue and cake side
            x = _config.Team == Team.Blue ? x : 3000 - x;
            y = 2000 - y;

            return new Point(x, y);
        }
        #endregion

        #region High level private methods (with watch)

        private bool[] FindCandlesFromClustersBySplittingPoints(TargetColor redTargetColor, TargetColor blueTargetColor, Dictionary<TargetColor, IEnumerable<Rectangle>> clusters, 
            out Dictionary<TargetColor, IEnumerable<Rectangle>> result)
        {
            _log.DebugStartWatch("FindCandlesFromClustersBySplittingPoints");

            int maxClusterHeidth = _config.IsTouchPro2() ? 20 : 10;
            int maxClusterWidth = _config.IsTouchPro2() ? 30 : 15;

            IEnumerable<Point> red = clusters[redTargetColor]
                .Where(r => r.Height < maxClusterHeidth && r.Width < maxClusterWidth)
                .Select(r => new Point(r.X + r.Width / 2, r.Y + r.Height / 2));
            IEnumerable<Point> blue = clusters[blueTargetColor]
                .Where(r => r.Height < maxClusterHeidth && r.Width < maxClusterWidth)
                .Select(r => new Point(r.X + r.Width / 2, r.Y + r.Height / 2));

            var all = red.Union(blue);

            var candles = SeparatePointsAndResolveCandle(all, red, blue, out result);

            _log.DebugElapsed("FindCandlesFromClustersBySplittingPoints");

            return candles;
        }

        private bool[] FindCandlesFromClustersByVectors(TargetColor yellowTargetColor, TargetColor blueTargetColor, TargetColor redTargetColor,
            Dictionary<TargetColor, IEnumerable<Rectangle>> clusters, out Dictionary<TargetColor, IEnumerable<Rectangle>> result)
        {
            _log.DebugStartWatch("FindCandlesFromClustersByVectors");

            int maxSize = _config.IsTouchPro2() ? 30 : 15;
            int maxVectorSize = _config.IsTouchPro2() ? 14 : 7;
            int sinusMin = 65;

            Point[] yellows = clusters[yellowTargetColor].Where(r => r.Height < maxSize && r.Width < maxSize)
                .Select(r => new Point(r.X + r.Width / 2, r.Y + r.Height / 2)).ToArray();
            Point[] blues = clusters[blueTargetColor].Where(r => r.Height < maxSize && r.Width < maxSize)
                .Select(r => new Point(r.X + r.Width / 2, r.Y + r.Height / 2)).ToArray();
            Point[] reds = clusters[redTargetColor].Where(r => r.Height < maxSize && r.Width < maxSize)
                .Select(r => new Point(r.X + r.Width / 2, r.Y + r.Height / 2)).ToArray();

            Point[][] bluePolarVectors = CalculateSinusVectors(yellows, blues);
            Point[][] redPolarVectors = CalculateSinusVectors(yellows, reds);

            var blueVectors = bluePolarVectors.SelectMany(l => l.ToList()).Where(v => v.Y < maxVectorSize && v.Y > 0 && v.X > sinusMin).ToList();
            var redVectors = redPolarVectors.SelectMany(l => l.ToList()).Where(v => v.Y < maxVectorSize && v.Y > 0 && v.X > sinusMin).ToList();

            var all = new List<Point>();

            foreach (var v in blueVectors)
            {
                int i, j;
                FindVector(bluePolarVectors, v, out i, out j);
                if (i != -1 && j != -1)
                    all.Add(blues[j]);
            }

            foreach (var v in redVectors)
            {
                int i, j;
                FindVector(redPolarVectors, v, out i, out j);
                if (i != -1 && j != -1)
                    all.Add(reds[j]);
            }

            var candles = SeparatePointsAndResolveCandle(all, reds, blues, out result);

            _log.DebugElapsed("FindCandlesFromClustersByVectors");

            return candles;
        }

        private void CountColorsInCandleAreas(List<TargetColor> targetColors, IEnumerable<Rectangle> candlesAreas, out double[] blueCounts, out double[] redCounts)
        {
            _log.DebugStartWatch("CountColorsInCandleAreas");

            blueCounts = new double[8];
            redCounts = new double[8];
            int cnt = 0;

            foreach (var area in candlesAreas)
            {
                blueCounts[cnt] = _clustering.Count(_mask, targetColors[0].Key, area);
                redCounts[cnt] = _clustering.Count(_mask, targetColors[1].Key, area);
                cnt++;
            }

            _log.DebugElapsed("CountColorsInCandleAreas");
        }

        private IEnumerable<Point> FindBeaconsFromClusters(TargetColor greenTargetColor, TargetColor magentaTargetColor, Dictionary<TargetColor, IEnumerable<Rectangle>> clusters,
            out Dictionary<TargetColor, IEnumerable<Rectangle>> result)
        {
            _log.DebugStartWatch("FindBeaconsFromClusters");

            int maxSize = _config.IsTouchPro2() ? 50 : 25;
            int maxVectorSize = _config.IsTouchPro2() ? 30 : 15;
            int sinusMin = 50;

            Point[] greens = clusters[greenTargetColor]
                .Where(r => r.Height < maxSize && r.Width < maxSize)
                .Select(r => new Point(r.X + r.Width / 2, r.Y + r.Height / 2)).ToArray();

            Point[] magentas = clusters[magentaTargetColor]
                .Where(r => r.Height < maxSize && r.Width < maxSize)
                .Select(r => new Point(r.X + r.Width / 2, r.Y + r.Height / 2)).ToArray();

            Point[][] polarVectors = CalculateSinusVectors(greens, magentas);

            var temp = polarVectors.SelectMany(l => l.ToList()).Where(v => v.Y < maxVectorSize && v.Y > 0).OrderBy(v => v.X).ToList();
            Point v1 = temp.Where(v => v.X > sinusMin).FirstOrDefault();
            Point v2 = temp.Where(v => v.X < -sinusMin).LastOrDefault();

            int i1 = -1, i2 = -1, j1 = -1, j2 = -1;

            FindVector(polarVectors, v1, out i1, out j1);
            FindVector(polarVectors, v2, out i2, out j2);

            result = new Dictionary<TargetColor, IEnumerable<Rectangle>>();
            var beacons = new List<Point>();

            if (i1 != -1)
            {
                result.Add(new TargetColor(Color.Orange, _pixelFormat), GetBeaconDisplayRectangles(greens[i1], magentas[j1], v1.Y, maxVectorSize));
                beacons.Add(GetBeaconPolarCoordinates(greens[i1], magentas[j1], v1.Y));
            }

            if (i2 != -1)
            {
                result.Add(new TargetColor(Color.BlueViolet, _pixelFormat), GetBeaconDisplayRectangles(greens[i2], magentas[j2], v2.Y, maxVectorSize));
                beacons.Add(GetBeaconPolarCoordinates(greens[i2], magentas[j2], v2.Y));
            }

            _log.DebugElapsed("FindBeaconsFromClusters");

            return beacons;
        }

        private void MatchTargetColor(IEnumerable<TargetColor> targetColors, Rectangle analyzeArea, byte[] buffer, byte[] mask, 
            int width, int height, int bytesPerPixel, PixelFormat pixelFormat)
        {
            _log.DebugStartWatch("MatchTargetColor");

            int stride = width * bytesPerPixel;

            if (buffer.Length != mask.Length || buffer.Length != stride * height)
                throw new ArgumentException();

            Array.Clear(mask, 0, mask.Length);

            for (int j = analyzeArea.Y; j < analyzeArea.Y + analyzeArea.Height; j++)
            {
                for (int i = analyzeArea.X; i < analyzeArea.X + analyzeArea.Width; i++)
                {
                    int index = j * stride + i * bytesPerPixel;
                    
                    byte low, high;
                    double hue, saturation, brightness;
                    
                    BitmapHelper.ConvertRGBTo565(buffer, index, pixelFormat, out low, out high);
                    HsvHelper.MapColor565ToHSL(low, high, out hue, out saturation, out brightness);

                    foreach (var targetColor in targetColors)
                    {
                        if (targetColor.Match(hue, saturation, brightness))
                        {
                            //BitmapHelper.SetPixel(targetColor.Display, mask, index, pixelFormat);
                            Array.Copy(targetColor.Key, 0, mask, index, bytesPerPixel);
                            break;
                        }
                    }
                }
            }

            _log.DebugElapsed("MatchTargetColor");
        }

        private void DrawResultImage(Dictionary<TargetColor, IEnumerable<Rectangle>> objects, byte[] buffer, byte[] mask, Bitmap bufferImage, Bitmap maskImage,
            Rectangle analyzeArea, PixelFormat pixelFormat, bool displayMask)
        {
            _log.DebugStartWatch("DrawResultImage");

            BitmapHelper.BufferToBitmap(buffer, bufferImage, pixelFormat);
            BitmapHelper.BufferToBitmap(mask, maskImage, pixelFormat);

            if (Log.IsInfoEnabled() && _dropCounter % 10 == 0)
            {
                _log.Debug("Saving frames");
                bufferImage.Save(Path.Combine(Log.LogFolder, @"image_" + _imageCounter + ".jpg"), ImageFormat.Jpeg);
            }

            if (displayMask)
                BitmapHelper.ComposeImages(bufferImage, maskImage, Color.Black);

            using (Graphics graph = Graphics.FromImage(bufferImage))
            {
                if (analyzeArea != null)
                    graph.DrawRectangle(new Pen(Color.White), analyzeArea);

                foreach (var targetColor in objects.Keys)
                {
                    foreach (var rect in objects[targetColor])
                    {
                        graph.DrawRectangle(new Pen(targetColor.Display), rect);
                    }
                }
            }

            if (Log.IsInfoEnabled() && _dropCounter++ % 10 == 0)
                bufferImage.Save(Path.Combine(Log.LogFolder, @"image_" + _imageCounter++ + "_result.jpg"), ImageFormat.Jpeg);

            _log.DebugElapsed("DrawResultImage");
        }
        #endregion

        #region Low level private methods

        #region For Split area
        private IEnumerable<Rectangle> ExtractCandleAreas(Rectangle analyzeArea, Team team)
        {
            _log.Debug("ExtractCandleAreas");

            int w = (int)(analyzeArea.Width / 5);
            int h = analyzeArea.Height / 2;

            for (int j = 1; j >= 0; j--)
            {
                if (team == Team.Blue)
                {
                    int offset = j == 0 ? analyzeArea.Width - w * 4 : 0;

                    for (int i = 0; i < 4; i++)
                    {
                        yield return new Rectangle(analyzeArea.X + offset + i * w, analyzeArea.Y + j * h, w, h);
                    }
                }
                else
                {
                    int offset = j != 0 ? analyzeArea.Width - w * 4 : 0;

                    for (int i = 3; i >= 0; i--)
                    {
                        yield return new Rectangle(analyzeArea.X + offset + i * w, analyzeArea.Y + j * h, w, h);
                    }
                }

            }
        }

        private void Normalize(double[] samples)
        {
            //_log.Debug("Normalize");

            if (samples.Length == 0)
                return;

            double mean = samples[0];
            double min = samples[0];
            double max = samples[0];

            for (int i = 0; i < samples.Length; i++)
            {
                mean += samples[i];

                if (samples[i] > max)
                    max = samples[i];

                if (samples[i] < min)
                    min = samples[i];
            }

            mean /= samples.Length;

            if (min == max)
                max++;

            for (int i = 0; i < samples.Length; i++)
            {
                samples[i] = (samples[i] - min) / (max - min);
            }
        }

        private bool[] ColorCountsToCandles(double[] blueCounts, double[] redCounts)
        {
            //_log.Debug("ColorCountsToCandles");

            double[] diff = new double[blueCounts.Length];

            for (int i = 0; i < blueCounts.Length; i++)
                diff[i] = redCounts[i] - blueCounts[i];

            var orderedIndexes = GetOrderDesc(diff);
            orderedIndexes.Remove(0); // known
            orderedIndexes.Remove(4); // known

            bool[] candles = new bool[10];
            bool iAmRed = _config.Team == Team.Red;

            int cnt = iAmRed ? 2 : 0; // count number of red candles already found (2 for known pos)
            foreach (int i in orderedIndexes)
            {
                int offset = i >= 4 ? 2 : 0;

                if (cnt < 4)
                    candles[i + offset] = iAmRed;
                else
                    candles[i + offset] = !iAmRed;

                cnt++;
            }

            // known values
            candles[0] = true;
            candles[4] = true; // white
            candles[5] = true; // white
            candles[6] = true;

            return candles;
        }

        private IList<int> GetOrderDesc(double[] samples)
        {
            var result = new List<int>();

            for (int i = 0; i < samples.Length; i++)
            {
                int iMax = -1;

                for (int j = 0; j < samples.Length; j++)
                {
                    if (!result.Contains(j) && (iMax == -1 || samples[j] > samples[iMax]))
                        iMax = j;
                }

                result.Add(iMax);
            }

            return result;
        }

        private Dictionary<TargetColor, IEnumerable<Rectangle>> PrepareDrawingCandlesFromAreas(IList<Rectangle> candlesAreas, bool[] candles)
        {
            //_log.DebugStartWatch("PrepareDrawing");

            var blueResults = new List<Rectangle>();
            var redResults = new List<Rectangle>();
            int k = 0;
            for (int i = 0; i < 8; i++)
            {
                var r = candlesAreas[i];
                var newR = new Rectangle(r.X, r.Y, r.Width - 1, r.Height - 1);

                if (_config.Team == Team.Red)
                {
                    if (candles[k])
                        redResults.Add(newR);
                    else
                        blueResults.Add(newR);
                }
                else
                {
                    if (candles[k])
                        blueResults.Add(newR);
                    else
                        redResults.Add(newR);
                }

                k++;
                if (k == 4)
                    k = 6;
            }

            var result = new Dictionary<TargetColor, IEnumerable<Rectangle>>();
            result.Add(_config.Blue, blueResults);
            result.Add(_config.Red, redResults);

            _log.DebugElapsed("PrepareDrawing");
            return result;
        }
        #endregion

        #region Vectors

        private Point[][] CalculateSinusVectors(Point[] firsts, Point[] seconds)
        {
            _log.Debug("CalculateSinusVectors");

            var vectors = new Point[firsts.Length][];

            for (int i = 0; i < firsts.Length; i++)
            {
                vectors[i] = new Point[seconds.Length];

                for (int j = 0; j < seconds.Length; j++)
                {
                    int x = seconds[j].X - firsts[i].X;
                    int y = seconds[j].Y - firsts[i].Y;

                    double d = Math.Sqrt(x * x + y * y);
                    //double a = x == 0 && y == 0 ? 0 : Math.Atan2(y, x) * 360 / 6.28318530;
                    //if (a < 0) 
                    //    a += 360;
                    double a = x == 0 && y == 0 ? 0 : y * 100 / d;

                    vectors[i][j] = new Point((int)a, (int)d);
                }
            }

            return vectors;
        }

        private bool FindVector(Point[][] polarVectors, Point vector, out int foundI, out int foundJ)
        {
            _log.Debug("FindVector");

            foundI = -1;
            foundJ = -1;

            for (int i = 0; i < polarVectors.Length; i++)
            {
                for (int j = 0; j < polarVectors[i].Length; j++)
                {
                    if (polarVectors[i][j] == vector)
                    {
                        foundI = i;
                        foundJ = j;
                        return true;
                    }
                }
            }

            return false;
        }
        #endregion

        #region Beacons

        private Point GetBeaconPolarCoordinates(Point point1, Point point2, int size)
        {
            _log.Debug("GetBeaconPolarCoordinates");

            int xImage = _config.Team == Team.Red ? (point1.X + point2.X) / 2 : _width - ((point1.X + point2.X) / 2);

            int aOrig = _config.IsTouchPro2() ? 7 : 7; // TODO Touch
            int aAmpl = _config.IsTouchPro2() ? 57 : 57; // TODO Touch

            double a = aOrig + (xImage * aAmpl) / _width;
            double d = 4270 - 210 * size;

            return new Point((int)a, (int)d);
        }

        private IEnumerable<Rectangle> GetBeaconDisplayRectangles(Point point1, Point point2, int size, int maxSize)
        {
            _log.Debug("GetBeaconDisplayRectangles");

            int x = (point1.X + point2.X) / 2;
            int y = (point1.Y + point2.Y) / 2;

            var result = new List<Rectangle> {
                new Rectangle(x, y - maxSize / 2, 1, maxSize),
                new Rectangle(x - size / 2, y - size / 2, size, size),
            };

            return result;
        }
        #endregion

        #region SeparatePointsAndResolveCandle for Split points

        private bool[] SeparatePointsAndResolveCandle(IEnumerable<Point> all, IEnumerable<Point> red, IEnumerable<Point> blue, out Dictionary<TargetColor, IEnumerable<Rectangle>> result)
        {
            _log.Debug("SeparatePointsAndResolveCandle");

            var ligne1 = SeparateUp(all);
            var ligne2 = all.Except(ligne1);

            IEnumerable<Point> grpLT = SeparateLeft(ligne1);
            IEnumerable<Point> grpRT = ligne1.Except(grpLT);
            IEnumerable<Point> grpLB = SeparateLeft(ligne2);
            IEnumerable<Point> grpRB = ligne2.Except(grpLB);

            IEnumerable<Point> c1 = SeparateLeft(grpLT);
            IEnumerable<Point> c2 = grpLT.Except(c1);
            IEnumerable<Point> c3 = SeparateLeft(grpRT);
            IEnumerable<Point> c4 = grpRT.Except(c3);

            IEnumerable<Point> c5 = SeparateLeft(grpLB);
            IEnumerable<Point> c6 = grpLB.Except(c5);
            IEnumerable<Point> c7 = SeparateLeft(grpRB);
            IEnumerable<Point> c8 = grpRB.Except(c7);

            var redCandles = new List<Rectangle>();
            var blueCandles = new List<Rectangle>();
            var greyCandles = new List<Rectangle>();

            AddCandle(c1, red, blue, redCandles, blueCandles, greyCandles);
            AddCandle(c2, red, blue, redCandles, blueCandles, greyCandles);
            AddCandle(c3, red, blue, redCandles, blueCandles, greyCandles);
            AddCandle(c4, red, blue, redCandles, blueCandles, greyCandles);
            AddCandle(c5, red, blue, redCandles, blueCandles, greyCandles);
            AddCandle(c6, red, blue, redCandles, blueCandles, greyCandles);
            AddCandle(c7, red, blue, redCandles, blueCandles, greyCandles);
            AddCandle(c8, red, blue, redCandles, blueCandles, greyCandles);

            result = new Dictionary<TargetColor, IEnumerable<Rectangle>>();
            result.Add(new TargetColor(Color.Orange, _pixelFormat), redCandles);
            result.Add(new TargetColor(Color.BlueViolet, _pixelFormat), blueCandles);
            result.Add(new TargetColor(Color.Gray, _pixelFormat), greyCandles);

            var candles = new bool[10];
            candles[0] = _config.Team == Team.Red ? TestCandle(c8, red, blue, true) : TestCandle(c5, red, blue, false);
            candles[1] = _config.Team == Team.Red ? TestCandle(c7, red, blue, true) : TestCandle(c6, red, blue, false);
            candles[2] = _config.Team == Team.Red ? TestCandle(c6, red, blue, true) : TestCandle(c7, red, blue, false);
            candles[3] = _config.Team == Team.Red ? TestCandle(c5, red, blue, true) : TestCandle(c8, red, blue, false);
            candles[4] = true;
            candles[5] = true;
            candles[6] = _config.Team == Team.Red ? TestCandle(c4, red, blue, true) : TestCandle(c1, red, blue, false);
            candles[7] = _config.Team == Team.Red ? TestCandle(c3, red, blue, true) : TestCandle(c2, red, blue, false);
            candles[8] = _config.Team == Team.Red ? TestCandle(c2, red, blue, true) : TestCandle(c3, red, blue, false);
            candles[9] = _config.Team == Team.Red ? TestCandle(c1, red, blue, true) : TestCandle(c4, red, blue, false);

            return candles;
        }

        private IEnumerable<Point> SeparateUp(IEnumerable<Point> all)
        {
            _log.Debug("SeparateUp");

            int y1 = all.Count() != 0 ? all.Select(p => p.Y).Min() : 0;
            int y2 = all.Count() != 0 ? all.Select(p => p.Y).Max() : 0;

            var ligne1 = all.Where(p => (p.Y - y1) < (y2 - p.Y));
            return ligne1;
        }

        private IEnumerable<Point> SeparateLeft(IEnumerable<Point> points)
        {
            _log.Debug("SeparateLeft");

            if (points.Count() == 0)
                return new List<Point>();

            int x1 = points.Select(p => p.X).Min();
            int x2 = points.Select(p => p.X).Max();

            return points.Where(p => (p.X - x1) < (x2 - p.X));
        }

        private void AddCandle(IEnumerable<Point> c1, IEnumerable<Point> red, IEnumerable<Point> blue, List<Rectangle> redCandles, List<Rectangle> blueCandles, List<Rectangle> greyCandles)
        {
            _log.Debug("AddCandle");

            if (c1.Count() == 0)
                return;

            int countR = c1.Where(p => red.Contains(p)).Count();
            int countB = c1.Where(p => blue.Contains(p)).Count();
            int x = (int)c1.Average(p => p.X);
            int y = (int)c1.Average(p => p.Y);
            var candle = new Rectangle(x - 3, y - 3, 6, 6);

            if (countR > countB)
                redCandles.Add(candle);
            else if (countR < countB)
                blueCandles.Add(candle);
            else
                greyCandles.Add(candle);
        }

        private bool TestCandle(IEnumerable<Point> c1, IEnumerable<Point> red, IEnumerable<Point> blue, bool teamRed)
        {
            _log.Debug("TestCandle");

            int countR = c1.Where(p => red.Contains(p)).Count();
            int countB = c1.Where(p => blue.Contains(p)).Count();

            if (countR > countB)
                return teamRed;
            else if (countR < countB)
                return !teamRed;
            else
                return true;
        }
        #endregion

        #endregion
    }
}
